#include "Renderer.h"
#include <boost/algorithm/string.hpp>
#include "Vector2.h"
#include "Matrix4.h"
#include <iostream>
#include <cmath>

namespace soft_renderer
{
	Renderer::Renderer(int w, int h)
	{
		width = w;
		height = h;
		bitmap = FreeImage_Allocate(width, height, 24);
		zbuffer = new float[width * height];
	}

	Renderer::~Renderer()
	{
		FreeImage_Unload(bitmap);
		delete[] zbuffer;
	}

	void Renderer::saveAs(std::string filename)
	{
		FREE_IMAGE_FORMAT format;
		if (boost::iends_with(filename, ".png"))
		{
			format = FIF_PNG;
		}
		else if (boost::iends_with(filename, ".jpg") || boost::iends_with(filename, ".jpeg"))
		{
			format = FIF_JPEG;
		}
		else if (boost::iends_with(filename, ".bmp"))
		{
			format = FIF_BMP;
		}
		else
		{
			throw "unsupported format";
		}

		FreeImage_Save(format, bitmap, filename.c_str(), 0);
	}


	Vec3f Renderer::barycentric(Vec3f A, Vec3f B, Vec3f C, Vec3f P)
	{
		Vec3f s[2];
		for (int i = 2; i--; )
		{
			s[i][0] = C[i] - A[i];
			s[i][1] = B[i] - A[i];
			s[i][2] = A[i] - P[i];
		}
		Vec3f u = cross(s[0], s[1]);
		if (std::abs(u[2]) > 1e-2) // dont forget that u[2] is integer. If it is zero then triangle ABC is degenerate
		{
			return { 1.f - (u[0] + u[1]) / u[2], u[1] / u[2], u[0] / u[2] };
		}
		return { -1, 1, 1 }; // in this case generate negative coordinates, it will be thrown away by the rasterizator
	}

	Vec3f Renderer::cross(Vec3f v1, Vec3f v2)
	{
		return { v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0] };
	}

	Vec3f Renderer::world2screen(Vector3 p)
	{
		return { float((p.x() + 1.0) * width / 2.0), float((p.y() + 1.0) * height / 2.0), float(p.z()) };
	}

	void Renderer::render(const Camera& camera, Mesh& mesh)
	{
		// 清空图片
		RGBQUAD bgColor;
		bgColor.rgbRed = 255;
		bgColor.rgbGreen = 255;
		bgColor.rgbBlue = 255;
		
		FreeImage_FillBackground(bitmap, &bgColor);
		// 清空zbuffer
		std::fill(zbuffer, zbuffer + width * height, 1.0);
		// 设置相关的matrix
		Matrix4 matrix;
		// M = viewMatrix * transformMatrix
		// M * v = viewMatrix * transformMatrix * v = viewMatrix * (transformMatrix * v)
		matrix.multiplyMatrices(camera.viewMatrix(), mesh.transformMatrix());
		// M = projectionMatrix * (viewMatrix * transformMatrix)
		// M * v = projectionMatrix * (viewMatrix * transformMatrix) * v = projectionMatrix * (viewMatrix * (transformMatrix * v))
		matrix.premultiply(camera.projectionMatrix());
		Matrix4 normaMatrix = mesh.normalMatrix();
		// 逐个绘制三角形
		const std::vector<float>& vertexArray = mesh.geometry().vertexArray();
		const std::vector<float>& normalArray = mesh.geometry().normalArray();
		bool hasNormal = normalArray.size() == vertexArray.size();
		const std::vector<uint32_t>& faceArray = mesh.geometry().faceArray();
		bool indexedFace = faceArray.size() > 0;

		std::srand((int)time(0));
		if (indexedFace)
		{
			int i1, i2, i3;
			Vector3 a, b, c;
			Vector3 na, nb, nc;
			// std::cout << "render: 0/" << faceArray.size() / 3 << std::flush;
			for (int i = 0; i < faceArray.size(); i += 3)
			{
				// std::cout << "\rrender: " << (i / 3) + 1 << "/" << faceArray.size() / 3 << std::flush;
				i1 = faceArray[i]; i2 = faceArray[i + 1]; i3 = faceArray[i + 2];
				a.set(vertexArray[i1 * 3], vertexArray[i1 * 3 + 1], vertexArray[i1 * 3 + 2]);
				b.set(vertexArray[i2 * 3], vertexArray[i2 * 3 + 1], vertexArray[i2 * 3 + 2]);
				c.set(vertexArray[i3 * 3], vertexArray[i3 * 3 + 1], vertexArray[i3 * 3 + 2]);
				na.set(normalArray[i1 * 3], normalArray[i1 * 3 + 1], normalArray[i1 * 3 + 2]);
				nb.set(normalArray[i2 * 3], normalArray[i2 * 3 + 1], normalArray[i2 * 3 + 2]);
				nc.set(normalArray[i3 * 3], normalArray[i3 * 3 + 1], normalArray[i3 * 3 + 2]);
				a.applyMatrix4(matrix);
				b.applyMatrix4(matrix);
				c.applyMatrix4(matrix);
				na.applyMatrix4(normaMatrix); na.normalize();
				nb.applyMatrix4(normaMatrix); nb.normalize();
				nc.applyMatrix4(normaMatrix); nc.normalize();
				//a.applyMatrix4(mesh.transformMatrix());
				//b.applyMatrix4(mesh.transformMatrix());
				//c.applyMatrix4(mesh.transformMatrix());

				//a.applyMatrix4(camera.viewMatrix());
				//b.applyMatrix4(camera.viewMatrix());
				//c.applyMatrix4(camera.viewMatrix());

				//a.applyMatrix4(camera.projectionMatrix());
				//b.applyMatrix4(camera.projectionMatrix());
				//c.applyMatrix4(camera.projectionMatrix());

				triangle(a, b, c, na, nb, nc);
			}
		}
	}

	void Renderer::triangle(Vector3 a, Vector3 b, Vector3 c, Vector3 na, Vector3 nb, Vector3 nc)
	{
		Vec3f a_screen, b_screen, c_screen; // position on screen
		a_screen = world2screen(a);
		b_screen = world2screen(b);
		c_screen = world2screen(c);

		// 背面剔除
		// https://zhuanlan.zhihu.com/p/403745946
		// https://blog.csdn.net/qq_40912854/article/details/80208906
		Vector2 ab = Vector2(b_screen[0] - a_screen[0], b_screen[1] - a_screen[1]);
		Vector2 ac = Vector2(c_screen[0] - a_screen[0], c_screen[1] - a_screen[1]);
		double ab_ac = ab.cross(ac);
		if (ab_ac <= 0) return;

		Vec2f bboxmin{ std::numeric_limits<float>::max(), std::numeric_limits<float>::max() };
		Vec2f bboxmax{ std::numeric_limits<float>::min(), std::numeric_limits<float>::min() };
		bboxmin[0] = std::min(bboxmin[0], a_screen[0]);
		bboxmax[0] = std::max(bboxmax[0], a_screen[0]);
		bboxmin[1] = std::min(bboxmin[1], a_screen[1]);
		bboxmax[1] = std::max(bboxmax[1], a_screen[1]);

		bboxmin[0] = std::min(bboxmin[0], b_screen[0]);
		bboxmax[0] = std::max(bboxmax[0], b_screen[0]);
		bboxmin[1] = std::min(bboxmin[1], b_screen[1]);
		bboxmax[1] = std::max(bboxmax[1], b_screen[1]);

		bboxmin[0] = std::min(bboxmin[0], c_screen[0]);
		bboxmax[0] = std::max(bboxmax[0], c_screen[0]);
		bboxmin[1] = std::min(bboxmin[1], c_screen[1]);
		bboxmax[1] = std::max(bboxmax[1], c_screen[1]);

		// bboxmin[0] = std::max(0.0f, bboxmin[0]);
		// bboxmax[0] = std::min(float(width), bboxmax[0]);
		// bboxmin[1] = std::max(0.0f, bboxmin[1]);
		// bboxmax[1] = std::min(float(height), bboxmax[1]);
		int x1 = std::max(0, int(std::floor(bboxmin[0])));
		int x2 = std::min(width - 1, int(std::ceil(bboxmax[0])));
		int y1 = std::max(0, int(std::floor(bboxmin[1])));
		int y2 = std::min(height - 1, int(std::ceil(bboxmax[1])));

		Vec3f P;
		RGBQUAD whiteColor;
		whiteColor.rgbRed = 255;
		whiteColor.rgbGreen = 255;
		whiteColor.rgbBlue = 255;

		RGBQUAD randomColor;
		randomColor.rgbRed = std::rand() % 256;
		randomColor.rgbGreen = std::rand() % 256;
		randomColor.rgbBlue = std::rand() % 256;

		//Vector3 a2(a_screen[0], a_screen[1], a_screen[2]);
		//Vector3 b2(b_screen[0], b_screen[1], b_screen[2]);
		//Vector3 c2(c_screen[0], c_screen[1], c_screen[2]);
		Vector3 ab2; ab2.subVectors(b, a);
		Vector3 ac2; ac2.subVectors(c, a);
		Vector3 norm; norm.crossVectors(ac2, ab2);
		norm.normalize();
		Vector3 light(0, 0, -1);
		float intensity = light.dot(norm);
		intensity = std::max(0.0f, intensity);

		RGBQUAD lightColor;
		lightColor.rgbRed = intensity * 255;
		lightColor.rgbGreen = intensity * 255;
		lightColor.rgbBlue = intensity * 255;

		Vector3 light2(-1, -1, -1);
		light2.normalize();

		Vector3 light3(1, 1, 1);
		light3.normalize();

		float intensity2 = std::max(0.0, light2.dot(na));
		float intensity3 = std::max(0.0, light3.dot(na));
		intensity2 += intensity3;
		intensity2 = std::min(1.0f, intensity2);

		RGBQUAD lightColor2;
		#if 0
		lightColor2.rgbRed = 0;
		lightColor2.rgbGreen = 0;
		lightColor2.rgbBlue = 0;
		#else
		lightColor2.rgbRed = intensity2 * 255;
		lightColor2.rgbGreen = intensity2 * 255;
		lightColor2.rgbBlue = intensity2 * 255;
		#endif

		for (int x = x1; x <= x2; x++)
		{
			P[0] = x;
			for (int y = y1; y <= y2; y++)
			{
				P[1] = y;
				Vec3f bc_screen = barycentric(a_screen, b_screen, c_screen, P);
				// 在三角形外边
				if (bc_screen[0] < 0 || bc_screen[1] < 0 || bc_screen[2] < 0) continue;
				P[2] = 0;
				P[2] += a_screen[2] * bc_screen[0];
				P[2] += b_screen[2] * bc_screen[1];
				P[2] += c_screen[2] * bc_screen[2];
				#if 0
				FreeImage_SetPixelColor(bitmap, P[0], P[1], &lightColor2);
				#else
				int zbuffer_index = x + y * width;
				if (P[2] <= zbuffer[zbuffer_index])
				{
					FreeImage_SetPixelColor(bitmap, P[0], P[1], &lightColor2);
					zbuffer[zbuffer_index] = P[2];
				}
				#endif
			}
		}
		// std::cout << "in tri: " << inTri << "/" << total << std::endl;
	}
}
